---
title: Configuration
---

One is entirely configured through a Vite plugin:

```tsx fileName=vite.config.ts
import type { UserConfig } from 'vite'
import { one } from 'one/vite'

export default {
  plugins: [
    one({
      // all web-specific configuration nests in web:
      web: {
        // Choose the default strategy for routes without path-specifiers to render in
        // can be one of "spa", "ssg" or "ssr"
        defaultRenderMode: 'spa', // defaults to 'ssg'

        // If using a server-rendered render mode, looseHydration will collapse large hydration
        // error logs in development mode. React can gracefully recover from hydration issues,
        // and they can often be ignored, but by default React produces large error logs.
        // Turning this on will collapse those logs to one line.
        looseHydration: false, // defaults to true

        // Define server redirects for development and production
        redirects: [
          {
            source: '/vite',
            destination: 'https://vxrn.dev',
            permanent: true,
          },
          {
            source: '/docs/components/:slug/:version',
            destination: '/ui/:slug/:version',
            permanent: true,
          },
        ],
      },

      /**
       * Path to a js or ts file to import before the rest of your app runs
       * One controls your root, but you may want to run some JS before anything else
       * Use this to give One the entrypoint to run
       */
      setupFile: './setup.ts',

      router: {
        /**
         * An array of globs that One uses to ignore files in your routes directory. If a file matches any pattern, it will not be treated as a route.
         *
         * This is useful for ignoring test or utility files you wish to colocate.
         */
        ignoredRouteFiles: ['**/*.test.*'],
      },

      optimization: {
        /**
         * Turn on [vite-plugin-barrel](https://github.com/JiangWeixian/vite-plugin-barrel/tree/master).
         * Optimizes barrel export files to speed up your build, you must list the packages that have
         * barrel exports. Especially useful for icon packs.
         *
         * @default ['@tamagui/lucide-icons']
         * @type boolean | string[]
         */
        barrel: ['my-icon-package']
      },

      // native-specific config:
      native: {
        // One will set up your React Native app to run via AppRegistry.registerComponent(app.key)
        // This setting determines app.key and must match the React Native app container you've built
        key: 'AppName',

        // Choose the bundler for native builds
        // - 'metro' is recommended for production stability.
        // - 'vite' is experimental but offers faster builds with SWC.
        bundler: 'metro', // defaults to 'vite'
      },

      config: {
        // Disable or configure the auto-added vite-tsconfig-paths
        // https://www.npmjs.com/package/vite-tsconfig-paths
        tsConfigPaths: false

        // Disable auto-adding default tsconfig.json if not found
        ensureTSConfig: false
      },

      server: {
        // Configures production server to be compatible (default node)
        platform: 'node' | 'vercel'
      },

      react: {
        // Enable react compiler (default false)
        compiler: true,
      },

      // The deps configuration is a powerful way to apply patches to node_modules
      // to configure patching and optimizeDeps for server-side.
      // we found the React Native and general node module package ecosystem to not always
      // play nice with universal apps, and this provides an easy way to configure them
      deps: {
        'moti/author': true,
        '@sentry/react-native': {
          version: '~15.2.0',
          '**/*.js': ['jsx']
        }
      },

     /**
      * Per-file control over how code transforms.
      * Defaults to SWC, runs babel before SWC if:
      *
      *  - options.react.compiler is `true`, on tsx files in your app
      *  - `react-native-reanimated` is in your dependencies and a file contains a reanimated keyword
      *
      * Otherwise One defaults to using `@swc/core`.
      *
      * Accepts a function:
      *
      *   (props: {
      *      id: string
      *      code: string
      *      development: boolean
      *      environment: Environment
      *      reactForRNVersion: '18' | '19'
      *   }) =>
      *      | true     // default transforms
      *      | false    // no transforms
      *      | 'babel'  // force babel default transform
      *      | 'swc'    // force swc default transform
      *
      *       // force babel, custom transform
      *
      *      | {
      *          transform: 'babel'
      *          excludeDefaultPlugins?: boolean
      *        } & babel.TransformOptions
      *
      *      // force swc, custom transform
      *
      *      | {
      *          transform: 'swc'
      *        } & SWCOptions
      *
      * Babel defaults to preset `@babel/preset-typescript` with plugins:
      *
      *  - @babel/plugin-transform-destructuring
      *  - @babel/plugin-transform-runtime
      *  - @babel/plugin-transform-react-jsx
      *  - @babel/plugin-transform-async-generator-functions
      *  - @babel/plugin-transform-async-to-generator
      *
      *
      * SWC default to target es5 for native, es2020 for web.
      *
      */
      transform: true
    })
  ]
} satisfies UserConfig
```

Note that some of this configuration is ultimately stored into [environment variables](/docs/environment) automatically, accessible in your apps and website.

## native

The native object allows you to configure your native apps.

### native.key

One will set up your React Native app to run via `AppRegistry.registerComponent(app.key)`.

This setting must match the key you gave the React Native app container you've built.

### native.bundler

Choose the bundler for native builds. Options are:

- `'metro'` (recommended): Uses the standard React Native Metro bundler for maximum stability and compatibility
- `'vite'` (experimental): Uses Vite for native bundling with faster builds via SWC

**We strongly recommend using Metro mode for production applications** as it provides:

- Full compatibility with all React Native packages
- Battle-tested stability
- Standard React Native bundling behavior

For detailed information including limitations, configuration options, and performance considerations, see the [Metro Mode documentation](/docs/metro-mode).

## web

The web object allows you to configure web specific settings.

### web.defaultRenderMode

Choose between one of `ssg`, `spa` or `ssr`. The default is `ssg`.

Each mode has trade-offs:

- `ssg` stands for "Static Site Generated", it is the default mode.

SSG is a great general purpose strategy, where your pages are rendered fully at build-time and turned into static HTML. This means you can get a "perfect" first load, where your app is fully rendered with all content, servable easily by a CDN.

The JS is sent alongside it, and React will hydrate your app and resume running from there. The downside is that you do have some complexity in needing to ensure your pages can render in node, the upside is increased performance and SEO on the web.

- `spa` stands for "Single Page App", which is the simplest strategy.

SPA mode will not render the page on the server at all, and instead only serve the JavaScript to render your page
to the users browser. This mode avoids some complexity - you don't have to make your React code

- `ssr` stands for "Sever Side Rendered".

This mode will render your page on the server at the time the browser requests it, running loaders as well for each request.

This mode is likely the least useful, as it means that you must make a full trip to your database at the time of each request, and your client is left waiting for the full render from the server before it can show anything. That said, this mode can be useful if you have dynamically generated content but still want to have the best possible SEO, at the cost of some complexity in supporting server rendering.

### redirects

You can apply server-level redirects. One serves your app with Hono in production, and so the redirect pattern is simple: your `source` will be passed to `Hono.app.get(source)`. Any matching path segments that start with `:` will be replaced into the matching segments in `destination`. And the redirect will send a response type of `301` if permanent is true, otherwise it will be `302`.

## react

### compiler: boolean

Enables the new [React Compiler](https://react.dev/learn/react-compiler) for both native and web. Will slow down compilation as it uses Babel, but will improve app performance and reduce the need to write memoization.

## deps

While building One we found ourselves needing to apply small patches to a variety of packages. Packages in the React Native ecosystem are often published only for Metro, and so have a variety of weird setups.

We built an internal Vite plugin to easily apply patches across any package manager, but after installing One into a few larger production projects, we found ourselves wanting to have access to it.

The `deps` option is a powerful way to coerce your node_modules to be compatible with Vite.

Here's an example:

```tsx fileName=vite.config.ts
import type { UserConfig } from 'vite'
import { one } from 'one/vite'

export default {
  plugins: [
    one({
      deps: {
        // setting to true is the same as setting:
        // viteConfig.ssr.optimizeDeps.include.push('moti/author')
        'moti/author': true,

        // setting to 'interop' is the same as setting:
        // viteConfig.ssr.optimizeDeps.include.push('node-fetch')
        // viteConfig.ssr.optimizeDeps.needsInterop.push('node-fetch')
        'node-fetch': 'interop',

        // passing an object lets you easily apply patches to your actual node_modules
        '@sentry/react-native': {
          // you can specify a semver range to only apply to certain versions:
          version: '~15.2.0',

          // you can use globs to apply to only specific files
          // if you pass an array value, One will apply one of two transforms to matching files:
          //    swc => @swc/core will transform your file into CommonJS compatible code, removing JSX as well
          //    flow => the FlowType transformer will run to remove Flow types
          '**/*.js': ['flow', 'swc']

          // you can also apply custom transforms using a function
          'dist/js/utils/environment.js': (contents) => {
            if (typeof contents !== 'string') return
            return contents.replace(
              `import { version as RNV } from 'react-native/Libraries/Core/ReactNativeVersion';`,
              `import { Platform } from 'react-native';\nconst RNV = Platform.constants.reactNativeVersion;\n`
            )
          },
        },
      },
    })
  ]
} satisfies UserConfig
```

Any transforms that are applied to specific files in node_modules will only be applied once. The original files will be stored alongside the transformed ones.

## router

The router object allows you to configure routing behavior.

### router.ignoredRouteFiles

An array of globs that One uses to ignore files in your routes directory. If a file matches any pattern, it will not be treated as a route.

This is particularly useful for ignoring test files, stories, or other files you want to colocate with your routes but don't want to be treated as actual routes.

```tsx fileName=vite.config.ts
export default {
  plugins: [
    one({
      router: {
        ignoredRouteFiles: [
          '**/*.test.*',
          '**/*.spec.*',
          '**/*.stories.*',
          '**/*.utilities.*',
        ],
      },
    }),
  ],
}
```

**Limitations:** Currently, we only support patterns starting with `**/*`.

### router.experimental.typedRoutesGeneration

Auto-generate route type helpers in your route files. Route types are always generated in `app/routes.d.ts`, but this option controls whether One automatically inserts type helpers into your route files.

```tsx fileName=vite.config.ts
export default {
  plugins: [
    one({
      router: {
        experimental: {
          typedRoutesGeneration: 'runtime', // or 'type' or false
        },
      },
    }),
  ],
}
```

**Options:**

- **`false`** (default): No auto-generation, manually add types yourself
- **`'type'`**: Auto-inserts type-only helpers:

```tsx
import type { RouteType } from 'one'
type Route = RouteType<'/your/[route]'>
```

- **`'runtime'`**: Auto-inserts runtime helpers:

```tsx
import { createRoute } from 'one'
const route = createRoute<'/your/[route]'>()
```

The insertion happens automatically when route files are created or modified, with proper spacing and without modifying your existing code.

For more information, see the [Typed Routes documentation](/docs/routing-typed-routes).

## The Environment API

One runs on Vite 6 and makes use of their [new Environment API](https://main.vitejs.dev/guide/api-environment).

This makes it easier to target React Native, as you can specify configuration for iOS, Android and web separately. One defines `ios` and `android` environments, respectively. For web, we follow the Vite standard of having a `client` and `ssr` environments (for client-side and server-side bundles).

You'll want to lean on using the Environment API to configure as much as you can in your `vite.config.ts`. It may also mean that some plugins can cause issues by configuring things across all four environments when you really only want it for client-side, or not. We're working on some helpers to make this easier, but in general you can check for `this.environment.name` in plugin hooks (like `transform` and `load`) to conditionally exit early from their logic for environments where you don't want the plugin logic to run.

One assumes the following environments:

- `client`: Web client-side (matching Vite default)
- `ssr`: Web server-side (matching Vite default)
- `ios`: iOS
- `android`: Android

We set up platform-specific extensions based on the environment:

- client/ssr: `.web.(js|ts|tsx|mjs)`
- ios: `.(ios|native).(js|ts|tsx|mjs)`
- android: `.(android|native).(js|ts|tsx|mjs)`

## Other Exports

The `one/vite` import has a few other exports that may be useful.

### `resolvePath`

When you are setting an `alias` in Vite, it wants you to fully specify the import path. We use `resolvePath` to help with this, it's a bit like [`require.resolve`](https://nodejs.org/api/modules.html) that works in ESM or CJS. It's also similar to [`import.meta.resolve`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta/resolve), except it returns an absolute path that Vite expects rather than a file path.

## Default Configuration

One sets up some default configuration out of the box, including a variety of plugins and settings that make React Native and the ecosystem of packages that are popular "just work".

Some base configuration we set:

- `publicDir` to `"public"`
- `clearScreen` to `false` so we can output the quick actions

We set up `defines`:

- `__DEV__` to true in development mode, or false otherwise
- `process.env.NODE_ENV`

We set up `alias` based on environment:

- `react-native` => `react-native-web` for web environments

We also detect if you have a PostCSS config file (js, ts, or json) and set the `css`. The internal logic is something like:

```tsx
{
  css: postCSSConfigPath
    ? {
        postcss: postCSSConfigPath,
      }
    : {
        transformer: 'lightningcss',
        lightningcss: {
          targets: {
            safari: (15 << 16) | (2 << 8),
          },
        },
      }
}
```
